#include "bitmap.h"

/**
 * 创建一个Bitmap结构，并为其分配空间
 * @param bmp
 * @param width
 * @param height
 * @param bitCount
 * @return
 */
int CreateBitmap(Bitmap *bmp, int width, int height, int bitCount) {
  bmp->width = width;
  bmp->height = height;
  bmp->bmih.biWidth = width;
  bmp->bmih.biHeight = height;

  bmp->widthStep = (int) ((width * bitCount + 31) / 32) * 4;
  bmp->imageSize = bmp->height * bmp->widthStep * sizeof(BYTE);

  if (bitCount == 24) {
    bmp->bitCount = 24;
    bmp->bmfh.bfType = 0x4d42;
    bmp->bmih.biBitCount = 24;
    bmp->bmfh.bfReserved1 = 0;
    bmp->bmfh.bfReserved2 = 0;
    bmp->bmih.biSize = 40;
    bmp->bmih.biPlanes = 1;
    bmp->bmfh.bfSize = 54 + height * bmp->widthStep;
    bmp->bmfh.bfOffBits = 54;
    bmp->bmih.biBitCount = 24;
    bmp->bmih.biSizeImage = bmp->imageSize;
    bmp->bmih.biClrUsed = 0;
    bmp->bmih.biCompression = 0;
    bmp->bmih.biClrImportant = 0;
    bmp->bmih.biXPelsPerMeter = 0;
    bmp->bmih.biYPelsPerMeter = 0;
  } else if (bitCount == 8) {
    bmp->bitCount = 8;
    bmp->bmfh.bfType = 0x4d42;
    bmp->bmfh.bfReserved1 = 0;
    bmp->bmfh.bfReserved2 = 0;
    bmp->bmih.biBitCount = 8;
    bmp->bmih.biSize = 40;
    bmp->bmih.biPlanes = 1;
    bmp->bmfh.bfSize = 54 + 256 * 4 + height * bmp->widthStep;
    bmp->bmfh.bfOffBits = 1078;
    bmp->bmih.biBitCount = 8;
    bmp->bmih.biCompression = 0;
    bmp->bmih.biSizeImage = bmp->imageSize;
    bmp->bmih.biClrUsed = 0;
    bmp->bmih.biClrImportant = 0;
    bmp->bmih.biXPelsPerMeter = 0;
    bmp->bmih.biYPelsPerMeter = 0;
  } else {
    printf("Error(CreateBitmap): Only supported 8 or 24 bits BMP file.\n");
    return -1;
  }

  bmp->imageData = (BYTE *) malloc(bmp->imageSize);
  if (!(bmp->imageData)) {
    printf("Error(CreateBitmap): Can't allocate BMP memory.\n");
    return -1;
  }

  return 0;
}

/**
 * 释放空间
 * @param bmp
 */
void ReleaseBitmap(Bitmap *bmp) {
  free(bmp->imageData);
  bmp->imageData = NULL;
  free(bmp);
  bmp = NULL;
}

/**
 * 路径检查：是否为BMP文件，是否可读
 * @param path
 * @return
 */
int CheckPath(char *path) {
  FILE *fd;
  int len = strlen(path) / sizeof(char);
  char ext[3];

  strncpy(ext, &path[len - 3], 3);

  if (!(ext[0] == 'b' && ext[1] == 'm' && ext[2] == 'p')) {
    printf("Error(CheckPath): The extension of the file is not bmp.\n");
  }

  fd = fopen(path, "r");
  if (!fd) {
    printf("Error(CheckPath): Can't open the file.\n");
    return -1;
  }

  fclose(fd);

  return 0;

}

/**
 * 从文件中读取BMP
 * @param path
 * @param bmp
 * @return
 */
int ReadBitmap(char *path, Bitmap *bmp) {
  int ret;
  FILE *fd;

  ret = CheckPath(path);
  if (ret == -1) {
    printf("Error(ReadBitmap): The path of the file is invalid.\n");
    return -1;
  }

  fd = fopen(path, "rb");
  if (fd == 0) {
    printf("Error(ReadBitmap): Can't open the file.\n");
    return -1;
  }

  //读取文件头
  fread(&(bmp->bmfh.bfType), sizeof(WORD), 1, fd);
  fread(&(bmp->bmfh.bfSize), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmfh.bfReserved1), sizeof(WORD), 1, fd);
  fread(&(bmp->bmfh.bfReserved2), sizeof(WORD), 1, fd);
  fread(&(bmp->bmfh.bfOffBits), sizeof(DWORD), 1, fd);

  //读取信息头
  fread(&(bmp->bmfh.bfSize), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biWidth), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biHeight), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biPlanes), sizeof(WORD), 1, fd);
  fread(&(bmp->bmih.biBitCount), sizeof(WORD), 1, fd);
  fread(&(bmp->bmih.biCompression), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biSizeImage), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biXPelsPerMeter), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biYPelsPerMeter), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biClrUsed), sizeof(DWORD), 1, fd);
  fread(&(bmp->bmih.biClrImportant), sizeof(DWORD), 1, fd);

  ret = CreateBitmap(bmp, bmp->bmih.biWidth, bmp->bmih.biHeight, bmp->bmih.biBitCount);
  if (ret == 1) {
    printf("Error(CreateBitmap): Can't create BMP file.\n");
    return -1;
  }

  fseek(fd, bmp->bmfh.bfOffBits, SEEK_SET);
  ret = fread(bmp->imageData, bmp->imageSize, 1, fd);
  if (ret == 0) {
    if (feof(fd)) {

    }
    if (ferror(fd)) {
      printf("Error(ReadBitmap): Can't read the data.\n");
      fclose(fd);
      return -1;
    }
  }


  fclose(fd);
  return 0;
}

/**
 * 保存BMP到文件
 * @param path
 * @param bmp
 * @return
 */
int SaveBitmap(char *path, Bitmap *bmp) {
  int ret;
  FILE *fd;

  int len = strlen(path) / sizeof(char);
  char ext[3];

  strncpy(ext, &path[len - 3], 3);
  if (!(ext[0] == 'b' && ext[1] == 'm' && ext[2] == 'p')) {
    printf("Error(SaveBitmap): The extension of the file is not bmp.\n");
    return -1;
  }

  fd = fopen(path, "wb");
  if (fd == 0) {
    printf("Error(SaveBitmap): Can't open the image.\n");
    return -1;
  }

  fwrite(&(bmp->bmfh.bfType), sizeof(WORD), 1, fd);
  fwrite(&(bmp->bmfh.bfSize), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmfh.bfReserved1), sizeof(WORD), 1, fd);
  fwrite(&(bmp->bmfh.bfReserved2), sizeof(WORD), 1, fd);
  fwrite(&(bmp->bmfh.bfOffBits), sizeof(DWORD), 1, fd);

  fwrite(&(bmp->bmih.biSize), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biWidth), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biHeight), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biPlanes), sizeof(WORD), 1, fd);
  fwrite(&(bmp->bmih.biBitCount), sizeof(WORD), 1, fd);
  fwrite(&(bmp->bmih.biCompression), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biSizeImage), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biXPelsPerMeter), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biYPelsPerMeter), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biClrUsed), sizeof(DWORD), 1, fd);
  fwrite(&(bmp->bmih.biClrImportant), sizeof(DWORD), 1, fd);

  RGBQUAD pal[256];
  int i;
  if (bmp->bitCount == 8) {
    for (i = 0; i < 256; i++) {
      pal[i].rgbBlue = i;
      pal[i].rgbGreen = i;
      pal[i].rgbRed = i;
      pal[i].rgbReserved = 0;
    }
    if (fwrite(pal, sizeof(RGBQUAD) * 256, 1, fd) != 1) {
      printf("Error(SaveBitmap): can not write Color Palette.\n");
      return -1;
    }
  }

  ret = fwrite(bmp->imageData, bmp->imageSize, 1, fd);
  if (ret != 1) {
    printf("Error(SaveBitmap): Can't save the data.\n");
    return -1;
  }

  fclose(fd);
  return 0;
}
